package business

import (
    "encoding/base64"
    "encoding/json"
    "errors"
    "fcs-api/src/app/model"
    "fcs-api/src/app/service/ksvc"
    "fcs-api/src/app/service/networkpolicy"
    "fcs-api/src/app/service/route"
    "fcs-api/src/app/service/vpc"
    k8s "fcs-api/src/client/kubernetes"
    "fmt"
    "io/ioutil"
    "net/http"
    "regexp"
    "strings"

    "git.inspur.com/x/common/loggerfactory"
    "github.com/google/uuid"
    corev1 "k8s.io/api/core/v1"
    k8serrors "k8s.io/apimachinery/pkg/api/errors"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var logger = loggerfactory.GetLogger()

func CreateFunction(fcClient *k8s.FcClient, funcInfo *model.FuncInfo, token, projectId string) (*model.FuncInfo, error) {
    var imageUrl string
    var err error
    if funcInfo.Runtime == model.CustomContainer {
        imageUrl, err = getCustomContainerImageUrl(funcInfo, fcClient, token, projectId)
    } else {
        imageUrl, err = getImageUrl(funcInfo.Runtime)
    }
    if err != nil {
        _ = logger.Error("CreateFunction: failed to get image url, %s", err.Error())
        return nil, err
    }
    logger.Debug("CreateFunction: imageUrl %s", imageUrl)

    vpcConfig := new(model.VpcConfig)
    //if funcInfo.EnableVpc == 1 {
    //	var err error
    //	vpcConfig, err = addVpcConfig(fcClient, funcInfo, token)
    //	if err != nil {
    //		_ = logger.Error("CreateFunction: add vpc config error, %v", err)
    //		return nil, err
    //	}
    //}
    _, err = ksvc.CreateKsvc(fcClient, funcInfo, imageUrl, vpcConfig)
    if err != nil {
        _ = logger.Error("CreateFunction: create ksvc error, %v", err)
        return nil, err
    }

    funcInfo.Status = model.FunctionCreating
    funcInfo, err = model.CreateFunction(funcInfo)
    if err != nil {
        _ = logger.Error("CreateFunction: insert function db error, %v", err)
        return nil, err
    }
    // 创建一个latest的版本插入数据库
    versionInfo := makeLatestVersion(funcInfo)
    _, err = model.CreateVersion(versionInfo)
    if err != nil {
        _ = logger.Error("CreateFunction: insert version db error, %v", err)
        return nil, err
    }
    // 异步更新函数状态
    go func() {
        _, err := ksvc.UpdateKsvcTraffic(fcClient, funcInfo)
        if err != nil {
            _ = logger.Error("CreateFunction: UpdateKsvc error, %s", err.Error())
            funcInfo.Status = model.FunctionFailure
        } else {
            funcInfo.Status = model.FunctionSuccess
        }
        _, err = model.UpdateFunctionStatus(funcInfo)
        if err != nil {
            _ = logger.Error("CreateFunction: UpdateFunctionStatus error, %s", err.Error())
        }
    }()
    return funcInfo, nil
}

func UpdateFunction(fcClient *k8s.FcClient, updateModel *model.UpdateModel, token string) (*model.FuncInfo, error) {

    funcInfo := updateModel.FuncInfo
    versionInfo := updateModel.VersionInfo
    var err error
    var vpcConfig *model.VpcConfig = nil
    //switch updateModel.VpcUpdateStatus {
    //case model.Vpc_Need_Add:
    //	//vpcConfig, err = addVpcConfig(fcClient, funcInfo, token)
    //case model.Vpc_Need_Delete:
    //	//err = removeVpcConfig(fcClient, funcInfo, updateModel.OldVcpInfo, token)
    //case model.Vpc_Need_Modify:
    //	//err = removeVpcConfig(fcClient, funcInfo, updateModel.OldVcpInfo, token)
    //	//if err == nil {
    //		//vpcConfig, err = addVpcConfig(fcClient, funcInfo, token)
    //	//}
    //default:
    //	break
    //}
    if err != nil {
        _ = logger.Error("UpdateFunction: vpc config error, %s", err.Error())
    }
    _, err = ksvc.UpdateKsvc(fcClient, updateModel, vpcConfig)
    if err != nil {
        _ = logger.Error("UpdateFunction: update ksvc error, %v", err)
        return nil, err
    }

    funcInfo.Status = model.FunctionUpdating
    funcInfo, err = model.UpdateFuncInfo(funcInfo)
    if err != nil {
        _ = logger.Error("UpdateFunction: update function db error, %v", err)
        return nil, err
    }

    if versionInfo.VersionId == "" {
        versionInfo.VersionId = uuid.New().String()
        _, err = model.CreateVersion(versionInfo)
        if err != nil {
            _ = logger.Error("UpdateFunction: create version db error, %v", err)
            return nil, err
        }
    } else {
        _, err = model.UpdateFuncVersion(versionInfo)
        if err != nil {
            _ = logger.Error("UpdateFunction: update version db error, %v", err)
            return nil, err
        }
    }
    // 异步更新函数状态
    go UpdateFunctionStatus(fcClient, funcInfo)
    return funcInfo, nil
}

func UpdateFunctionStatus(fcClient *k8s.FcClient, funcInfo *model.FuncInfo) {
    _, err := ksvc.UpdateKsvcTraffic(fcClient, funcInfo)
    if err != nil {
        _ = logger.Error("UpdateFunction: UpdateKsvc error, %s", err.Error())
        funcInfo.Status = model.FunctionFailure
    } else {
        funcInfo.Status = model.FunctionSuccess
    }
    _, err = model.UpdateFunctionStatus(funcInfo)
    if err != nil {
        _ = logger.Error("UpdateFunction: UpdateFunctionStatus error, %s", err.Error())
    }
}

func GetFunctionList(functionListQuery *model.FunctionListQuery) (*model.FunctionListResult, error) {
    funcInfoList, err := model.GetFuncInfoListByGroup(functionListQuery)
    if err != nil {
        _ = logger.Error("GetFunctionList: query db list error, %v", err)
        return nil, err
    }

    count := len(funcInfoList)
    pageStart := (functionListQuery.PageNo - 1) * functionListQuery.PageSize
    pageEnd := functionListQuery.PageNo * functionListQuery.PageSize
    if pageStart > count-1 {
        pageStart = 0
        pageEnd = 0
    } else if pageEnd > count {
        pageEnd = count
    }

    // 兼容前端莫名其妙的逻辑
    recordsFiltered := functionListQuery.PageSize
    if count == 0 {
        recordsFiltered = 0
    }

    fp := &model.FunctionListResult{
        TotalCount:      count,
        RecordsFiltered: recordsFiltered,
        Result:          funcInfoList[pageStart:pageEnd],
    }
    return fp, nil
}

func DeleteFunction(fcClient *k8s.FcClient, funcInfo *model.FuncInfo, token string) error {

    versionList, err := model.GetVersionList(funcInfo.Id)
    if err != nil {
        _ = logger.Error("DeleteFunction: get function version from db error, %s", err.Error())
        return err
    }
    go func() {
        // 遍历所有版本，查看是否绑定了vpc
        for _, version := range versionList {
            if version.EnableVpc == 1 {
                //funcInfo.VersionName = version.VersionName
                //err := removeVpcConfig(fcClient, funcInfo, &version.VpcInfo, token)
                //if err != nil {
                //	_ = logger.Error("DeleteFunction: remove vpc error, %v", err)
                //}
            }
        }
    }()
    err = ksvc.DeleteKsvc(fcClient, funcInfo)
    if err != nil {
        _ = logger.Error("DeleteFunction: delete ksvc error, %v", err)
        return err
    }

    // 删除函数记录
    _, err = model.DeleteFuncInfo(funcInfo)
    if err != nil {
        _ = logger.Error("DeleteFunction: delete funcInfo db error, %v", err)
        return err
    }
    funcInfoJson, _ := json.Marshal(funcInfo)
    logger.Info("function deleted, data: %v", string(funcInfoJson))
    // 删除函数对应的版本记录
    line, err := model.DeleteFunctionVersionByFunc(funcInfo.Id)
    if err != nil {
        _ = logger.Error("DeleteFunction: delete funcVersion db error, %v", err)
        return err
    } else {
        logger.Info("DeleteFunction: function version deleted, affected line ", line)
    }

    // 删除函数的别名
    count, err := model.GetAliasCount(funcInfo.Id)
    if err != nil {
        _ = logger.Error("DeleteFunction: delete funcVersion db error, %v", err)
        return err
    } else if count > 0 {
        err := route.DeleteFuncRoute(fcClient, funcInfo)
        if err != nil {
            _ = logger.Error("DeleteFunction: delete route error, %v", err)
            return err
        }
        _, err = model.DeleteFunctionAliasByFunc(funcInfo.Id)
        if err != nil {
            _ = logger.Error("DeleteFunction: delete alias db error, %v", err)
            return err
        }
    }

    return nil
}

func addVpcConfig(fcClient *k8s.FcClient, funcInfo *model.FuncInfo, token string) (*model.VpcConfig, error) {
    // 查找该vpc之前是否已经配置过
    has, existBinding, err := model.FindPortBinding(funcInfo.VpcInfo.VpcId)
    if err != nil {
        _ = logger.Error("addVpcConfig: check port binding error, %s", err.Error())
        return nil, err
    }
    // 如果配置过，直接复用之前的配置
    if has {
        server, err := model.GetServerById(existBinding.ServerId)
        if err != nil {
            _ = logger.Error("addVpcConfig: get server info error, %s", err.Error())
            return nil, err
        }
        namespace := ksvc.GetFunctionNamespace(funcInfo.A5tId, funcInfo.GroupName)
        err = networkpolicy.CreateVpcNetworkPolicy(fcClient, namespace, funcInfo.FunctionName, funcInfo.VersionName, existBinding.VpcCidr)
        if err != nil {
            _ = logger.Error("addVpcConfig: Create VPC networkpolicy error, %s", err.Error())
            return nil, err
        }
        line, err := model.AddPortBindingNumber(funcInfo.VpcInfo.VpcId)
        if err != nil {
            _ = logger.Error("addVpcConfig: add vpc binding number err, %s", err.Error())
            return nil, err
        }
        if line == 0 {
            _ = logger.Error("addVpcConfig: didn't find vpc binding")
            return nil, errors.New("Didn't find vpc binding. ")
        }
        return &model.VpcConfig{
            Eth:     existBinding.PortName,
            Cidr:    existBinding.VpcCidr,
            Gateway: existBinding.SubnetGwIp,
            Remote:  server.ServerIp,
            Mac:     existBinding.PortMac,
        }, nil
    }

    sc, err := model.GetAvailableServer()
    if err != nil {
        _ = logger.Error("addVpcConfig: get available server error, %s", err.Error())
        return nil, err
    }
    if sc.Count >= model.ServerPortNumber {
        _ = logger.Error("addVpcConfig: port number reach max")
        return nil, errors.New("port number reach max")
    }
    server, err := model.GetServerById(sc.Id)
    if err != nil {
        _ = logger.Error("addVpcConfig: get server info error, %s", err.Error())
        return nil, err
    }
    binding, err := vpc.AddPortBinding(funcInfo.VpcInfo.VpcId, funcInfo.VpcInfo.SubnetId, server.Id, token)
    if err != nil {
        _ = logger.Error("addVpcConfig: add port binding error, %s", err.Error())
        return nil, err
    }
    /**
      todo 下面的这段代码暂时屏蔽，由于获取不到集群master节点密码，所以等可以获取密码的时候再打开
    */
    //portName, err := vpc.GetPortName(server, binding.PortMac)
    // 如果查询不成功，那么使用mac地址作为路由依据
    //if err != nil {
    //	_ = logger.Warn("addVpcConfig: get port name error %s", err.Error())
    //}
    //binding.PortName = portName

    binding.Id = uuid.New().String()
    binding.FunctionNum = 1
    binding, err = model.CreatePortBinding(binding)
    if err != nil {
        _ = logger.Error("addVpcConfig: insert binding info error %s", err.Error())
        return nil, err
    }
    // 设置networkpolicy，把vpc的网段加到白名单当中
    // todo 需要考虑这种情况，当创建完成networkpolicy的时候，如果后面有步骤失败，那么再次创建相同的函数的时候，这里会失败
    namespace := ksvc.GetFunctionNamespace(funcInfo.A5tId, funcInfo.GroupName)
    err = networkpolicy.CreateVpcNetworkPolicy(fcClient, namespace, funcInfo.FunctionName, funcInfo.VersionName, binding.VpcCidr)
    if err != nil {
        _ = logger.Error("CreateFunction: Create VPC networkpolicy error, %s", err.Error())
    }
    return &model.VpcConfig{
        Eth:     binding.PortName,
        Cidr:    binding.VpcCidr,
        Gateway: binding.SubnetGwIp,
        Remote:  server.ServerIp,
        Mac:     binding.PortMac,
    }, nil
}

func removeVpcConfig(fcClient *k8s.FcClient, funcInfo *model.FuncInfo, vpcInfo *model.VpcInfo, token string) error {
    // todo 如果是主备的情况，那这里会不止一条记录
    has, binding, err := model.FindPortBinding(vpcInfo.VpcId)
    if err != nil {
        _ = logger.Error("removeVpcConfig: check port binding error, %s", err.Error())
        return err
    }
    if !has {
        _ = logger.Error("removeVpcConfig: didn't find the binding data in db, vpcid: %s", vpcInfo.VpcId)
    }
    // 如果小于1，那么删除记录并解绑
    if binding.FunctionNum <= 1 {
        line, err := model.DeletePortBinding(binding.VpcId)
        if err != nil {
            _ = logger.Error("removeVpcConfig: delete port binding from db error, %s", err.Error())
            return err
        }
        // 如果没有删除，那么可能是并发操作又增加了绑定数量，直接忽略
        if line != 0 {
            err = vpc.RemovePortBinding(binding.VpcId, binding.SubnetId, binding.ServerId, binding.PortId, token)
            if err != nil {
                _ = logger.Error("removeVpcConfig: delete port binding from vpc error, %s", err.Error())
                return err
            }
        }
    } else {
        // 在数据库中减少一条绑定记录
        line, err := model.CutPortBindingNumber(vpcInfo.VpcId)
        if err != nil {
            _ = logger.Error("removeVpcConfig: cut port binding number from db error, %s", err.Error())
            return err
        }
        if line == 0 {
            _ = logger.Error("removeVpcConfig: cut number error, may has Concurrent")
            return errors.New("remove VpcConfig occur Concurrent")
        }
    }
    namespace := ksvc.GetFunctionNamespace(funcInfo.A5tId, funcInfo.GroupName)
    _ = networkpolicy.DeleteVpcNetworkPolicy(fcClient, namespace, funcInfo.FunctionName, funcInfo.VersionName)
    return nil
}

func makeLatestVersion(funcInfo *model.FuncInfo) *model.FuncVersion {
    funcVersion := new(model.FuncVersion)
    funcVersion.VersionId = uuid.New().String()
    funcVersion.FuncId = funcInfo.Id
    funcVersion.FuncName = funcInfo.FunctionName
    funcVersion.GroupId = funcInfo.GroupId
    funcVersion.VersionName = model.FunctionLatestVersion
    funcVersion.Description = funcInfo.Description
    funcVersion.MemorySize = funcInfo.MemorySize
    funcVersion.GpuNum = funcInfo.GpuNum
    funcVersion.GpuType = funcInfo.GpuType
    funcVersion.Envs = funcInfo.Envs
    funcVersion.Timeout = funcInfo.Timeout
    funcVersion.Handler = funcInfo.Handler
    funcVersion.Concurrency = funcInfo.Concurrency
    funcVersion.ReserveNumber = funcInfo.ReserveNumber
    funcVersion.EnablePublic = funcInfo.EnablePublic
    funcVersion.EnableVpc = funcInfo.EnableVpc
    funcVersion.VpcInfo = funcInfo.VpcInfo
    funcVersion.ContainerImage = funcInfo.ContainerImage
    funcVersion.ContainerPort = funcInfo.ContainerPort
    funcVersion.ContainerCommand = funcInfo.ContainerCommand
    funcVersion.ContainerArgs = funcInfo.ContainerArgs
    funcVersion.InitHandler = funcInfo.InitHandler
    funcVersion.InitTimeout = funcInfo.InitTimeout
    funcVersion.BindingLayers = funcInfo.BindingLayers

    return funcVersion
}

func getImageUrl(runtime string) (string, error) {
    switch runtime {
    case model.Python36:
        return model.Base.RuntimeImage.Python36RuntimeUrl, nil
    case model.NodeJs1022:
        return model.Base.RuntimeImage.NodeJS1022RuntimeUrl, nil
    case model.Python27:
        return model.Base.RuntimeImage.Python27RuntimeUrl, nil
    case model.NodeJS1218:
        return model.Base.RuntimeImage.NodeJS1218RuntimeUrl, nil
    case model.Golang1:
        return model.Base.RuntimeImage.Golang1RuntimeUrl, nil
    case model.Java8:
        return model.Base.RuntimeImage.Java8RuntimeUrl, nil
    case model.PHP:
        return model.Base.RuntimeImage.PhpRuntimeUrl, nil
    default:
        return "", model.InvalidParameterError(model.FunctionCreateError, "没有找到对应的运行环境")
    }
}

func getCustomContainerImageUrl(funcInfo *model.FuncInfo, fcClient *k8s.FcClient, token, projectId string) (string, error) {
    if funcInfo.ContainerImage == "" {
        err := fmt.Errorf("getCustomContainerImageUrl error missing containerImage parameter")
        return "", err
    }
    // custom-container：
    // 1. 组装容器镜像地址
    // 2. 检查相应 Namespace 中是否有对应的 Secret fcs-registry-credential，没有则创建
    // 3. TODO: 验证 Secret 是否有镜像拉取权限
    imageUrl := model.Base.ContainerImageRegistry + "/" + funcInfo.ContainerImage
    namespace := model.FunctionNamespacePrefix + "-" + funcInfo.A5tId + "-" + funcInfo.GroupName

    _, err := fcClient.GetSecret(namespace, model.DefaultRegistrySecretName)
    if err != nil {
        if k8serrors.IsNotFound(err) {
            // Secret 不存在，需要创建
            secretData, err := getCirDockerConfigJson(token, projectId)
            if err != nil {
                return "", err
            }
            secret := &corev1.Secret{
                ObjectMeta: metav1.ObjectMeta{
                    Name: model.DefaultRegistrySecretName,
                },
                Data: map[string][]byte{
                    corev1.DockerConfigJsonKey: []byte(secretData),
                },
                Type: corev1.SecretTypeDockerConfigJson,
            }
            logger.Debug("getCustomContainerImageUrl create secret for namespace %s", namespace)
            secret, err = fcClient.CreateSecret(namespace, secret)
            if err != nil {
                logger.Error("getCustomContainerImageUrl error %s", err.Error())
                return "", err
            }
        } else {
            return "", err
        }
    }
    return imageUrl, nil
}

func getCirDockerConfigJson(token, projectId string) (string, error) {
    url := model.Base.CirEndpoint + "cir/images/v1/secret"
    r, err := http.NewRequest("GET", url, nil)
    if err != nil {
        _ = logger.Error("getCirDockerConfigJson NewRequest error %v", err)
        return "", err
    }
    r.Header.Set("Authorization", token)
    r.Header.Set("X-Project", projectId)
    resp, err := http.DefaultClient.Do(r)
    if err != nil {
        _ = logger.Error("getCirDockerConfigJson Client.Do error %v", err)
        return "", err
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        _ = logger.Error("getCirDockerConfigJson ReadAll error %v", err)
        return "", err
    }
    // 先 base64 解码
    // 再 正则匹配获取用户名密码
    // 最后 组装 kubernetes.io/dockerconfigjson
    bodyB64 := string(body)
    bodyB64 = strings.Replace(bodyB64, "\"", "", -1)
    loginInfo, err := base64.StdEncoding.DecodeString(bodyB64)
    if err != nil {
        _ = logger.Error("getCirDockerConfigJson DecodeString error %v", err)
        return "", err
    }
    logger.Debug(string(loginInfo))
    reg := regexp.MustCompile("docker login (.*) -u (.*) -p (.*)")
    if reg == nil {
        err = fmt.Errorf("failed to init regexp")
        _ = logger.Error("getCirDockerConfigJson regexp.MustCompile error %v", err)
        return "", err
    }
    regResult := reg.FindStringSubmatch(string(loginInfo))
    if len(regResult) != 4 {
        err = fmt.Errorf("failed to do regexp")
        _ = logger.Error("getCirDockerConfigJson FindStringSubmatch error %v", err)
        return "", err
    }
    registryUserName := regResult[2]
    registryPassword := regResult[3]
    registryAuth := base64.StdEncoding.EncodeToString([]byte(registryUserName + ":" + registryPassword))
    logger.Debug("registryUserName", registryUserName, "registryPassword", registryPassword)
    dockerConfigJson := fmt.Sprintf("{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"fcs@inspur.com\",\"auth\":\"%s\"}}}",
        model.Base.ContainerImageRegistry, registryUserName, registryPassword, registryAuth)
    logger.Debug(dockerConfigJson)
    return dockerConfigJson, nil
}
